package com.ticket.sass.common.rpc.ticketserver

import com.google.gson.Gson
import com.ticket.sass.common.entity.tenant.TicketServerConfigEntity
import com.ticket.sass.common.entity.tenant.enums.TicketServerConfigPlatform
import com.ticket.sass.common.rpc.dto.input.ticketserver.*
import com.ticket.sass.common.rpc.dto.output.ResultWrapOutput
import com.ticket.sass.common.rpc.exceptions.ApiStatusException
import com.ticket.sass.common.rpc.exceptions.ConfigException
import com.ticket.sass.common.ticketserver.dto.input.OrderTicketInfoInput
import com.ticket.sass.common.ticketserver.dto.input.SeatLockInput
import com.ticket.sass.common.ticketserver.dto.input.SeatUnlockInput
import com.ticket.sass.common.ticketserver.dto.output.*
import org.springframework.http.HttpStatus
import org.springframework.web.reactive.function.client.*
import org.springframework.web.util.UriBuilder
import reactor.core.publisher.Mono


/**
 * @author 朱其鹏
 * @date 2024/3/24 15:51
 */
class Client {
    private lateinit var serverUrl: String
    private lateinit var platform: TicketServerConfigPlatform
    private lateinit var config: String

    /**
     * 构建 web 请求
     */
    private fun buildClient(): WebClient {
        return WebClient.builder()
            .filter(ExchangeFilterFunction.ofResponseProcessor {
                if (it.statusCode() != HttpStatus.OK) {
                    throw ApiStatusException(it.statusCode().value().toString(), it.statusCode().name)
                }
                Mono.just(it)
            })
            .baseUrl(serverUrl)
            .defaultHeader("Platform", platform.value.toString())
            .defaultHeader("Config", config)
            .build()
    }

    /**
     * 设置服务配置
     *
     * @param config 数据库配置数据
     */
    fun setConfig(config: TicketServerConfigEntity?): Client {
        config ?: throw ConfigException("1", "配置不允许为 null")

        this.serverUrl = config.serverUrl
        this.platform = config.platform
        this.config = config.data

        return this
    }

    /**
     * 获取影厅数据
     */
    fun halls( subscribe: ((ResultWrapOutput<HallOutput>) -> Unit)? = null): Mono<ResultWrapOutput<HallOutput>> {
        return get("/basic-data/halls", null)
            .bodyToMono<ResultWrapOutput<HallOutput>>()
            .also { mono ->
                subscribe?.also { mono.subscribe(it) }
            }
    }

    /**
     * 获取排期数据
     */
    fun schedules(
        input: ScheduleInput,
        subscribe: ((ResultWrapOutput<ScheduleOutput>) -> Unit)? = null
    ): Mono<ResultWrapOutput<ScheduleOutput>> {
        return get("/basic-data/schedules", input)
            .bodyToMono<ResultWrapOutput<ScheduleOutput>>()
            .also { mono ->
                subscribe?.also { mono.subscribe(it) }
            }
    }

    /**
     * 获取排期座位图
     */
    fun scheduleSeatMap(
        input: ScheduleSeatMapInput,
        subscribe: ((ResultWrapOutput<ScheduleSeatMapOutput>) -> Unit)? = null
    ) : Mono<ResultWrapOutput<ScheduleSeatMapOutput>> {
        return get("/basic-data/schedules/seat-map", input)
            .bodyToMono<ResultWrapOutput<ScheduleSeatMapOutput>>()
            .also { mono ->
                subscribe?.also { mono.subscribe(it) }
            }
    }

    /**
     * 座位锁定
     */
    fun seatLock(
        input: SeatLockInput,
        subscribe: ((ResultWrapOutput<SeatLockOutput>) -> Unit)? = null
    ) : Mono<ResultWrapOutput<SeatLockOutput>> {
        return post("/trade/seat-lock", input)
            .bodyToMono<ResultWrapOutput<SeatLockOutput>>()
            .also { mono ->
                subscribe?.also { mono.subscribe(it) }
            }
    }

    /**
     * 座位解锁
     */
    fun seatUnlock(
        input: SeatUnlockInput,
        subscribe: ((ResultWrapOutput<SeatUnlockOutput>) -> Unit)? = null
    ) : Mono<ResultWrapOutput<SeatUnlockOutput>> {
        return post("/trade/seat-unlock", input)
            .bodyToMono<ResultWrapOutput<SeatUnlockOutput>>()
            .also { mono ->
                subscribe?.also { mono.subscribe(it) }
            }
    }

    /**
     * 影票下单
     */
    fun orderTicketSubmit(
        input: OrderTicketSubmitInput,
        subscribe: ((ResultWrapOutput<OrderTicketSubmitOutput>) -> Unit)? = null
    ) : Mono<ResultWrapOutput<OrderTicketSubmitOutput>> {
        return post("/trade/order-tickets", input)
            .bodyToMono<ResultWrapOutput<OrderTicketSubmitOutput>>()
            .also { mono ->
                subscribe?.also { mono.subscribe(it) }
            }
    }

    /**
     * 影票订单状态查询
     */
    fun orderTicketStatus(
        input: OrderTicketStatusInput,
        subscribe: ((ResultWrapOutput<OrderTicketStatusOutput>) -> Unit)? = null
    ) : Mono<ResultWrapOutput<OrderTicketStatusOutput>> {
        return get("/trade/order-tickets/status", input)
            .bodyToMono<ResultWrapOutput<OrderTicketStatusOutput>>()
            .also { mono ->
                subscribe?.also { mono.subscribe(it) }
            }
    }

    /**
     * 影票订单信息查询
     *
     * @param code 取票码
     */
    fun orderTicketInfo(
        code: String,
        subscribe: ((ResultWrapOutput<OrderTicketInfoOutput>) -> Unit)? = null
    ) : Mono<ResultWrapOutput<OrderTicketInfoOutput>> {
        return get("/trade/order-tickets/${code}")
            .bodyToMono<ResultWrapOutput<OrderTicketInfoOutput>>()
            .also { mono ->
                subscribe?.also { mono.subscribe(it) }
            }
    }

    /**
     * 影票订单退单
     */
    fun orderTicketRefund(
        input: OrderTicketRefundInput,
        subscribe: ((ResultWrapOutput<OrderTicketRefundOutput>) -> Unit)? = null
    ) : Mono<ResultWrapOutput<OrderTicketRefundOutput>> {
        return put("/trade/order-tickets/refund", input)
            .bodyToMono<ResultWrapOutput<OrderTicketRefundOutput>>()
            .also { mono ->
                subscribe?.also { mono.subscribe(it) }
            }
    }

    /**
     * GET 请求
     *
     * @param path      请求路径
     * @param input     请求参数
     */
    private fun get(
        path: String,
        input: Any? = null,
    ): WebClient.ResponseSpec {
        return buildClient()
            .get()
            .uri { uriBuild ->
                uriBuild.path(path)
                input?.also { uriBuild.inputToQueryParam(it) }
                uriBuild.build()
            }
            .retrieve()
    }

    /**
     * PUT 请求
     *
     * @param path      请求路径
     * @param input     请求参数
     */
    private fun put(
        path: String,
        input: Any,
    ) : WebClient.ResponseSpec {
        return buildClient()
            .put()
            .uri { it.path(path).build() }
            .bodyValue(input)
            .retrieve()
    }

    /**
     * POST 请求
     *
     * @param path      请求路径
     * @param input     请求参数
     */
    private fun post(
        path: String,
        input: Any,
    ) : WebClient.ResponseSpec {
        return buildClient()
            .post()
            .uri { it.path(path).build() }
            .bodyValue(input)
            .retrieve()
    }

    /**
     * input 转 query 参数
     *
     * @param input
     */
    private fun UriBuilder.inputToQueryParam(input: Any): UriBuilder {
        val map = Gson().fromJson(Gson().toJson(input), Map::class.java)

        map.forEach {
            queryParam(it.key.toString(), it.value.toString())
        }

        return this
    }
}